package net.sourceforge.peertwitter.core.applications.communication;

import net.sourceforge.peertwitter.core.events.MessageDelivery;
import net.sourceforge.peertwitter.scamp.messaging.JoinMessage;
import net.sourceforge.peertwitter.scamp.messaging.JoinMessageAck;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Before;
import org.junit.Test;

import resources.LiveNodeHandle;
import rice.environment.Environment;
import rice.environment.time.TimeSource;
import rice.environment.time.simulated.DirectTimeSource;
import rice.p2p.commonapi.Endpoint;
import rice.pastry.Id;

public class ScampImplTest {

	private long timestamp;
	private Environment fixedTimeEnv;
	private SwarmContainer swarmContainer;
	protected Swarm disabledSwarm;
	private SwarmFactory factory;
	private Mockery context;
	private Endpoint endpoint;
	private LiveNodeHandle joiningNode;
	private LiveNodeHandle swarmOwner;

	@Before
	public void createTimestampAndSetTimeEnvironment() {
	timestamp = System.currentTimeMillis();
		
		fixedTimeEnv = new Environment() {
			@Override
			public TimeSource getTimeSource() {
				return new DirectTimeSource(timestamp);
			}
		};
		
		context = new Mockery();
		swarmContainer = context.mock(SwarmContainer.class);
		factory = context.mock(SwarmFactory.class);
		disabledSwarm = context.mock(Swarm.class);
		endpoint = context.mock(Endpoint.class);
		joiningNode= new LiveNodeHandle(Id.build("0x1234"));
		swarmOwner = new LiveNodeHandle(Id.build("0xabcde"));
	}
	
	@Test
	public void aSelfJoinRequestCreatesADisabledGroupAndAddsItToSwarmContainer() {
		
		context.checking(new Expectations() {
			{
				allowing(endpoint).getLocalNodeHandle();
				will(returnValue(joiningNode.getId()));
				
				allowing(endpoint).getId();
				will(returnValue(swarmOwner.getId()));
				
				one(factory).createSwarm(false);
				will(returnValue(disabledSwarm));
				
				one(swarmContainer).addSwarm(disabledSwarm);
			}
		});
		MBGroupCommunication scamp = new ScampImpl(fixedTimeEnv, endpoint, factory, swarmContainer);
		scamp.join(swarmOwner.getId());
		context.assertIsSatisfied();
	}
	
	@Test
	public void aJoinIsRoutedTowardsTheGroupIdWithTheRightTimestamp() {
		final Id joiningNodeId = Id.build("0x1234");
		final Id followed = Id.build("0xabcde");

		
		context.checking(new Expectations() {
			{
				one(endpoint).getLocalNodeHandle();
				will(returnValue(new LiveNodeHandle(joiningNodeId)));

				one(endpoint).getId();
				will(returnValue(joiningNodeId));

				one(endpoint).route(followed, new JoinMessage(new LiveNodeHandle(joiningNodeId), followed, timestamp), null);
			}
		});
		MBGroupCommunication groupc = new ScampImpl(fixedTimeEnv, endpoint, factory, swarmContainer);
		groupc.join(followed);
		context.assertIsSatisfied();
	}
	
	@Test
	public void aReceivedJoinRequestToTheParticipantSwarmCreatesAndActivatesTheSwarmAndRepliesTheJoiningNode() {
		final JoinMessage jm = new JoinMessage(joiningNode, swarmOwner.getId(), timestamp);
		final Swarm aSwarm = context.mock( Swarm.class, "MockedSwarm");
		
		context.checking(new Expectations() {
			{
				one(endpoint).route(null, new JoinMessageAck(jm, swarmOwner), joiningNode);
				
				allowing(endpoint).getId();
				will(returnValue(swarmOwner.getId()));

				allowing(endpoint).getLocalNodeHandle();
				will(returnValue(swarmOwner));
				
				allowing(swarmContainer).exists(swarmOwner.getId());
				will(returnValue(false));
				
				one(factory).createSwarm(true);
				will(returnValue(aSwarm));
				
				one(swarmContainer).addSwarm(aSwarm);
			}
		});
		MBGroupCommunication groupc = new ScampImpl(fixedTimeEnv, endpoint, factory, swarmContainer);
		
		groupc.deliver(new MessageDelivery(swarmOwner.getId(), new JoinMessage(joiningNode, swarmOwner.getId(), timestamp)));
		context.assertIsSatisfied();
		
	}
	
	@Test
	public void aReceivedJoinRequestToADisabledGroupWhichTheReceiverIsTheManagerEnablesIt() {
		final JoinMessage jm = new JoinMessage(joiningNode, swarmOwner.getId(), timestamp);
		
		context.checking(new Expectations() {
			{
				one(endpoint).route(null, new JoinMessageAck(jm, swarmOwner), joiningNode);
				
				allowing(endpoint).getId();
				will(returnValue(swarmOwner.getId()));

				allowing(endpoint).getLocalNodeHandle();
				will(returnValue(swarmOwner));
				
				allowing(swarmContainer).exists(swarmOwner.getId());
				will(returnValue(true));
				
				allowing(swarmContainer).get(swarmOwner.getId());
				will(returnValue(disabledSwarm));
				
				allowing(disabledSwarm).isEnabled();
				will(returnValue(false));
				
				one(disabledSwarm).enable();
			}
		});
		MBGroupCommunication groupc = new ScampImpl(fixedTimeEnv, endpoint, factory, swarmContainer);
		
		groupc.deliver(new MessageDelivery(swarmOwner.getId(), new JoinMessage(joiningNode, swarmOwner.getId(), timestamp)));
		context.assertIsSatisfied();
	}
	
	
	@Test
	public void aReceivedJoinRequestToASwarmOtherThanTheNodeSwarmIsIgnored() {
		final Id idFromOtherSwarm = Id.build("0xaaaa");
		
		context.checking(new Expectations() {
			{
				allowing(endpoint).getId();
				will(returnValue(swarmOwner.getId()));
				
				never(factory);
			}
		});
		
		MBGroupCommunication groupc = new ScampImpl(fixedTimeEnv, endpoint, factory, swarmContainer);
		
		groupc.deliver(new MessageDelivery(swarmOwner.getId(), new JoinMessage(joiningNode, idFromOtherSwarm, timestamp)));
		
		
		context.assertIsSatisfied();
	}
	
	@Test
	public void aReceivedJoinRequestToAnIdOtherThanTheNodeIsIgnored() {
		
		final Id idDifferentFromSwarmId = Id.build("0xaaaa");
		
		context.checking(new Expectations() {
			{
				allowing(endpoint).getId();
				will(returnValue(swarmOwner.getId()));
				
				never(factory);
			}
		});
		
		MBGroupCommunication groupc = new ScampImpl(fixedTimeEnv, endpoint, factory, swarmContainer);
		
		groupc.deliver(new MessageDelivery(idDifferentFromSwarmId, new JoinMessage(joiningNode, idDifferentFromSwarmId, timestamp)));
		
		context.assertIsSatisfied();
	}

}
